﻿using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Serialization;
using Catel.Data;

namespace CLP.Entities
{
    [Serializable]
    public class FuzzyFactorCard : ACLPArrayBase
    {
        #region Constructors

        /// <summary>
        /// Initializes <see cref="FuzzyFactorCard" /> from scratch.
        /// </summary>
        public FuzzyFactorCard() { }

        /// <summary>
        /// Initializes <see cref="FuzzyFactorCard" /> from
        /// </summary>
        /// <param name="parentPage">The <see cref="CLPPage" /> the <see cref="FuzzyFactorCard" /> belongs to.</param>
        /// <param name="columns">The number of columns in the <see cref="FuzzyFactorCard" />.</param>
        /// <param name="rows">The number of rows in the <see cref="FuzzyFactorCard" />.</param>
        /// <param name="dividend">The total number the <see cref="FuzzyFactorCard" /> represents.</param>
        /// <param name="isRemainderRegionDisplayed">Signifies the <see cref="FuzzyFactorCard" /> is using a <see cref="RemainderRegion" />.</param>
        public FuzzyFactorCard(CLPPage parentPage, int columns, int rows, int dividend, bool isRemainderRegionDisplayed = false)
            : base(parentPage, columns, rows)
        {
            Dividend = dividend;
            IsSnappable = true;
            if(!isRemainderRegionDisplayed)
            {
                return;
            }
            RemainderTiles = new RemainderTiles(parentPage, this);
            parentPage.PageObjects.Add(RemainderTiles);
            UpdateRemainderRegion();
        }

        /// <summary>
        /// Initializes <see cref="FuzzyFactorCard" /> based on <see cref="SerializationInfo" />.
        /// </summary>
        /// <param name="info"><see cref="SerializationInfo" /> that contains the information.</param>
        /// <param name="context"><see cref="StreamingContext" />.</param>
        public FuzzyFactorCard(SerializationInfo info, StreamingContext context)
            : base(info, context) { }

        #endregion //Constructors

        #region Properties

        public override double LabelLength
        {
            get { return 35; }
        }

        public double LargeLabelLength
        {
            get { return LabelLength * 2 + 12.0; }
        }

        public override double ArrayWidth
        {
            get { return Width - (LargeLabelLength + LabelLength); }
        }

        public override double ArrayHeight
        {
            get { return Height - (2 * LabelLength); }
        }

        public override double GridSquareSize
        {
            get { return ArrayWidth / Columns; }
        }

        public override bool IsBackgroundInteractable
        {
            get { return true; }
        }

        private const double MIN_ARRAY_LENGTH = 185.0;

        public override double MinimumHeight
        {
            get { return MIN_ARRAY_LENGTH + (2 * LabelLength); }
        }

        public override double MinimumWidth
        {
            get { return MIN_ARRAY_LENGTH + LargeLabelLength + LabelLength; }
        }

        public override double MinimumGridSquareSize
        {
            get { return Columns < Rows ? MIN_ARRAY_LENGTH / Columns : MIN_ARRAY_LENGTH / Rows; }
        }

        public int GroupsSubtracted
        {
            get { return VerticalDivisions.Sum(division => division.Value); }
        }

        public int CurrentRemainder
        {
            get { return Dividend - GroupsSubtracted * Rows; }
        }

        public double LastDivisionPosition
        {
            get { return VerticalDivisions.Any() ? VerticalDivisions.Last().Position : 0.0; }
        }

        /// <summary>
        /// The total number the <see cref="FuzzyFactorCard" /> represents.
        /// </summary>
        public int Dividend
        {
            get { return GetValue<int>(DividendProperty); }
            set
            {
                SetValue(DividendProperty, value);
                RaisePropertyChanged("CurrentRemainder");
            }
        }

        public static readonly PropertyData DividendProperty = RegisterProperty("Dividend", typeof(int), 1);

        #region Navigation Properties

        /// <summary>
        /// Unique Identifier for the <see cref="FuzzyFactorCard" />'s <see cref="RemainderTiles" />.
        /// </summary>
        /// <remarks>
        /// Composite Foreing Key.
        /// </remarks>
        public string RemainderTilesID
        {
            get { return GetValue<string>(RemainderTilesIDProperty); }
            set { SetValue(RemainderTilesIDProperty, value); }
        }

        public static readonly PropertyData RemainderTilesIDProperty = RegisterProperty("RemainderTilesID", typeof(string), string.Empty);

        /// <summary>
        /// Unique Identifier of the <see cref="Person" /> who owns the <see cref="RemainderTiles" />.
        /// </summary>
        public string RemainderTilesOwnerID
        {
            get { return GetValue<string>(RemainderTilesOwnerIDProperty); }
            set { SetValue(RemainderTilesOwnerIDProperty, value); }
        }

        public static readonly PropertyData RemainderTilesOwnerIDProperty = RegisterProperty("RemainderTilesOwnerID", typeof(string), string.Empty);

        /// <summary>
        /// Version Index for the <see cref="RemainderTiles" />.
        /// </summary>
        public uint RemainderTilesVersionIndex
        {
            get { return GetValue<uint>(RemainderTilesVersionIndexProperty); }
            set { SetValue(RemainderTilesVersionIndexProperty, value); }
        }

        public static readonly PropertyData RemainderTilesVersionIndexProperty = RegisterProperty("RemainderTilesVersionIndex", typeof(uint), 0);

        /// <summary>
        /// <see cref="RemainderTiles" /> for the <see cref="FuzzyFactorCard" />.
        /// </summary>
        /// <remarks>
        /// Virtual to facilitate lazy loading of navigation property by Entity Framework.
        /// </remarks>
        public virtual RemainderTiles RemainderTiles
        {
            get { return GetValue<RemainderTiles>(RemainderTilesProperty); }
            set
            {
                SetValue(RemainderTilesProperty, value);
                if(value == null)
                {
                    RemainderTilesID = string.Empty;
                    RemainderTilesOwnerID = string.Empty;
                    RemainderTilesVersionIndex = 0;
                    return;
                }
                RemainderTilesID = value.ID;
                RemainderTilesOwnerID = value.OwnerID;
                RemainderTilesVersionIndex = value.VersionIndex;
            }
        }

        public static readonly PropertyData RemainderTilesProperty = RegisterProperty("RemainderTiles", typeof(RemainderTiles));

        #endregion //Navigation Properties

        #endregion //Properties

        #region Methods

        public override IPageObject Duplicate()
        {
            var newFuzzyFactorCard = Clone() as FuzzyFactorCard;
            if(newFuzzyFactorCard == null)
            {
                return null;
            }
            newFuzzyFactorCard.CreationDate = DateTime.Now;
            newFuzzyFactorCard.ID = Guid.NewGuid().ToCompactID();
            newFuzzyFactorCard.VersionIndex = 0;
            newFuzzyFactorCard.LastVersionIndex = null;
            newFuzzyFactorCard.ParentPage = ParentPage;

            return newFuzzyFactorCard;
        }

        public override void OnResized(double oldWidth, double oldHeight)
        {
            base.OnResized(oldWidth, oldHeight);
            RaisePropertyChanged("LastDivisionPosition");
        }

        public override void OnDeleted()
        {
            base.OnDeleted();

            var tag = new DivisionTemplateDeletedTag(ParentPage, Origin.StudentPageObjectGenerated, Dividend, Rows);
            ParentPage.AddTag(tag);
        }

        public override void SizeArrayToGridLevel(double toSquareSize = -1, bool recalculateDivisions = true)
        {
            var initialWidth = Width;
            var initialHeight = Height;
            var initialSquareSize = 45.0;
            if(toSquareSize <= 0)
            {
                while(XPosition + LabelLength + LargeLabelLength + initialSquareSize * Columns >= ParentPage.Width ||
                      YPosition + 2 * LabelLength + initialSquareSize * Rows >= ParentPage.Height)
                {
                    initialSquareSize = Math.Abs(initialSquareSize - 45.0) < .0001 ? 22.5 : initialSquareSize / 4 * 3;
                }
            }
            else
            {
                initialSquareSize = toSquareSize;
            }

            Height = (initialSquareSize * Rows) + 2 * LabelLength;
            Width = (initialSquareSize * Columns) + LabelLength + LargeLabelLength;

            if(recalculateDivisions)
            {
                ResizeDivisions();
            }
            OnResized(initialWidth, initialHeight);
        }

        public void AnalyzeArrays()
        {
            var arrayArea = 0;
            foreach(var pageObject in ParentPage.PageObjects)
            {
                if(pageObject is CLPArray &&
                   !(pageObject is FuzzyFactorCard))
                {
                    arrayArea += (pageObject as CLPArray).Rows * (pageObject as CLPArray).Columns;
                    if((pageObject as CLPArray).Columns == Dividend ||
                       ((pageObject as CLPArray).Rows == Dividend))
                    {
                        var existingTag =
                            ParentPage.Tags.OfType<DivisionTemplateIncorrectArrayCreationTag>()
                                      .FirstOrDefault(x => x.Value == DivisionTemplateIncorrectArrayCreationTag.AcceptedValues.ProductAsDimension);

                        var previousNumberOfAttempts = 0;
                        if(existingTag != null)
                        {
                            previousNumberOfAttempts = existingTag.NumberOfAttempts;
                            ParentPage.RemoveTag(existingTag);
                        }
                        var newTag = new DivisionTemplateIncorrectArrayCreationTag(ParentPage,
                                                                                   Origin.StudentPageObjectGenerated,
                                                                                   DivisionTemplateIncorrectArrayCreationTag.AcceptedValues.ProductAsDimension,
                                                                                   previousNumberOfAttempts + 1);
                        ParentPage.AddTag(newTag);
                    }
                    if((pageObject as CLPArray).Rows != Rows &&
                       (pageObject as CLPArray).Columns == Rows)
                    {
                        var existingTag =
                            ParentPage.Tags.OfType<DivisionTemplateIncorrectArrayCreationTag>()
                                      .FirstOrDefault(x => x.Value == DivisionTemplateIncorrectArrayCreationTag.AcceptedValues.WrongOrientation);

                        var previousNumberOfAttempts = 0;
                        if(existingTag != null)
                        {
                            previousNumberOfAttempts = existingTag.NumberOfAttempts;
                            ParentPage.RemoveTag(existingTag);
                        }
                        var newTag = new DivisionTemplateIncorrectArrayCreationTag(ParentPage,
                                                                                   Origin.StudentPageObjectGenerated,
                                                                                   DivisionTemplateIncorrectArrayCreationTag.AcceptedValues.WrongOrientation,
                                                                                   previousNumberOfAttempts + 1);
                        ParentPage.AddTag(newTag);
                    }
                    else if((pageObject as CLPArray).Rows != Rows)
                    {
                        var existingTag =
                            ParentPage.Tags.OfType<DivisionTemplateIncorrectArrayCreationTag>()
                                      .FirstOrDefault(x => x.Value == DivisionTemplateIncorrectArrayCreationTag.AcceptedValues.IncorrectDimension);

                        var previousNumberOfAttempts = 0;
                        if(existingTag != null)
                        {
                            previousNumberOfAttempts = existingTag.NumberOfAttempts;
                            ParentPage.RemoveTag(existingTag);
                        }
                        var newTag = new DivisionTemplateIncorrectArrayCreationTag(ParentPage,
                                                                                   Origin.StudentPageObjectGenerated,
                                                                                   DivisionTemplateIncorrectArrayCreationTag.AcceptedValues.IncorrectDimension,
                                                                                   previousNumberOfAttempts + 1);
                        ParentPage.AddTag(newTag);
                    }
                }
            }
            if(arrayArea > CurrentRemainder)
            {
                var existingTag =
                    ParentPage.Tags.OfType<DivisionTemplateIncorrectArrayCreationTag>().FirstOrDefault(x => x.Value == DivisionTemplateIncorrectArrayCreationTag.AcceptedValues.ArrayTooLarge);

                var previousNumberOfAttempts = 0;
                if(existingTag != null)
                {
                    previousNumberOfAttempts = existingTag.NumberOfAttempts;
                    ParentPage.RemoveTag(existingTag);
                }
                var newTag = new DivisionTemplateIncorrectArrayCreationTag(ParentPage,
                                                                           Origin.StudentPageObjectGenerated,
                                                                           DivisionTemplateIncorrectArrayCreationTag.AcceptedValues.ArrayTooLarge,
                                                                           previousNumberOfAttempts + 1);
                ParentPage.AddTag(newTag);
            }
        }

        public void UpdateRemainderRegion()
        {
            if(RemainderTiles == null)
            {
                return;
            }

            if(CurrentRemainder <= 0)
            {
                ParentPage.PageObjects.Remove(RemainderTiles);
                return;
            }

            if(!ParentPage.PageObjects.Contains(RemainderTiles))
            {
                ParentPage.PageObjects.Add(RemainderTiles);
            }

            var numberOfBlackTiles = ParentPage.PageObjects.Where(pageObject => pageObject is CLPArray && (pageObject as CLPArray).ArrayType == ArrayTypes.Array).Sum(pageObject =>
                                                                                                                                                                      {
                                                                                                                                                                          var clpArray =
                                                                                                                                                                              pageObject as CLPArray;
                                                                                                                                                                          return clpArray != null
                                                                                                                                                                                     ? clpArray.Rows *
                                                                                                                                                                                       clpArray.Columns
                                                                                                                                                                                     : 0;
                                                                                                                                                                      });
            numberOfBlackTiles = Math.Min(numberOfBlackTiles, CurrentRemainder);

            if(RemainderTiles.TileColors == null)
            {
                RemainderTiles.TileColors = new ObservableCollection<string>();
            }
            else
            {
                RemainderTiles.TileColors.Clear();
            }
            for(var i = 0; i < CurrentRemainder - numberOfBlackTiles; i++)
            {
                RemainderTiles.TileColors.Add("DodgerBlue");
            }
            for(var i = 0; i < numberOfBlackTiles; i++)
            {
                RemainderTiles.TileColors.Add("Black");
            }

            RemainderTiles.Height = Math.Ceiling(RemainderTiles.TileColors.Count / 5.0) * 61.0;
            RemainderTiles.Width = 305.0;
        }

        public void SnapInArray(int value)
        {
            var position = LastDivisionPosition + value * (ArrayHeight / Rows);
            var divAbove = FindDivisionAbove(position, VerticalDivisions);
            var divBelow = FindDivisionBelow(position, VerticalDivisions);

            CLPArrayDivision topDiv;
            if(divAbove == null)
            {
                topDiv = new CLPArrayDivision(ArrayDivisionOrientation.Vertical, 0, position, value);
            }
            else
            {
                topDiv = new CLPArrayDivision(ArrayDivisionOrientation.Vertical, divAbove.Position, position - divAbove.Position, value);
                VerticalDivisions.Remove(divAbove);
            }
            VerticalDivisions.Add(topDiv);
            CLPArrayDivision bottomDiv;
            bottomDiv = divBelow == null
                            ? new CLPArrayDivision(ArrayDivisionOrientation.Vertical, position, ArrayWidth - position, 0)
                            : new CLPArrayDivision(ArrayDivisionOrientation.Vertical, position, divBelow.Position - position, 0);
            VerticalDivisions.Add(bottomDiv);
            UpdateRemainderRegion();

            RaisePropertyChanged("GroupsSubtracted");
            RaisePropertyChanged("CurrentRemainder");
            RaisePropertyChanged("LastDivisionPosition");
        }

        public void RemoveLastDivision()
        {
            if(VerticalDivisions.Count <= 1)
            {
                return;
            }
            var lastDiv = VerticalDivisions.Last();
            var prevDiv = VerticalDivisions[VerticalDivisions.Count - 2];

            VerticalDivisions.Add(new CLPArrayDivision(ArrayDivisionOrientation.Vertical, prevDiv.Position, prevDiv.Length + lastDiv.Length, 0));
            VerticalDivisions.Remove(lastDiv);
            VerticalDivisions.Remove(prevDiv);

            RaisePropertyChanged("GroupsSubtracted");
            RaisePropertyChanged("CurrentRemainder");
            RaisePropertyChanged("LastDivisionPosition");

            UpdateRemainderRegion();
        }

        #endregion //Methods
    }
}